How to Develop a Flutter Plugin
@neonankiti
https://gyazo.com/05bfb8c905a8517e0eff0acff5f9f3a6
自己紹介
https://gyazo.com/27bdad70541d1f9c6bd4867bd179fdc0
南里勇気(なんりゆうき)
あだ名
Bison
職業
iOS Developer
Android Developer
会社紹介
https://gyazo.com/cb4d096fe5f36d7e4ebdc16cd942147b
本日話すこと
Flutter Pluginの具体的な開発手順
* InterfaceとNative(iOS/Android)がどのようにブリッジされているかは説明しません
そもそも今日の登壇の発端
社内にFlutter勉強会チーム(5人程度)がある
技術書典5でFlutter本を出すことが決定
せっかくだから成果を発表
Flutterとは
Google製のモバイルクロスプラットフォーム(Android/iOS)SDK
Dart言語
Flutter Pluginとは
Channelを介したメッセージング
https://gyazo.com/3789021c84e88e00447e267086156318
Native Bridgingのための実装群
Dart(インターフェース)
iOS/Android(具体実装)
package数は30個
ヘルスケアアプリを作ってみることに
https://gyazo.com/94aca8c68e519d73cd48d00415bc200f
HealthKit/Google FitのPluginがない
Native(iOS/Android)でヘルスケアデータを取得する方法は2つ
2. 端末内専用アプリ
Flutterはセンサー用のPluginしか提供していない => Pluginの開発が必要に
Plugin開発手順
1. プラットフォーム間のInterfaceの決定
2. Plugin Development
Interface
iOS/Android
3. Interface利用者の実装
Plugin開発環境セットアップ
コード
code:setup
$ flutter create --org com.bison --template=plugin -i swift -a kotlin health_fit
オプションをつけないと、デフォルトはObjective-C, Javaになってしまう。
iOS/Android側に以下のコードが生成
SwiftHealthFitPlugin.swift
HealthFitPlugin.kt
ビルド
code:build
cd health_fit/example; flutter build apk
https://gyazo.com/0eb548e51c6426b47fd91745f890d355
Interfaceの設計
lib側に設定(health_fit.dart)
Interfaceを決定するには、iOS/AndroidのFrameworkへの実装レベルでの理解が必要
フルスタックじゃないとPlugin開発は無理?
そんなことはない
各プラットフォームごとに作業を分担する
開発プロセス自体は、一般的なウェブ開発のサーバー、クライアントの関係性に近いので役割分担が可能
汎用的なUseCaseから定義する
iOS/Androidであるものから考えるのではなく、UseCaseを定義して適合する。
ヘルスケアデータの場合
ヘルスケアデータへのアクセス権限要求
追加、取得、削除、更新
ヘルスケアデータへのアクセス権限解除
ヘルスケアデータへのアクセス権限要求
必須パラメータを定義する
DataType
Step
Weight
PermissionType
Read
Write
上記を決定し、iOS/Androidで問題ないかチェック
問題なければ実装に入る
アクセス権限要求のコード
Interface全体像
code:health_fit.dart
static Future<bool> requestPermission(DataType type) async {
final bool requestPermission = await _channel.invokeMethod(
'requestPermission', {"dataType": type.toString(), "permission": 0});
return requestPermission;
}
Android(iOS)のメソッドを呼び出す
Android側のコールバック(MethodChannel)に渡す変数を入れる
code:health_fit.dart
// 第一引数にメソッド名を入れる
_channel.invokeMethod('requestPermission', ...)
Flutterアプリ側への返り値を決定
ヘルスケアデータのパーミッションが成功したか、失敗したか知りたいのでboolを設定
code:health_fit.dart
static Future<bool> requestPermission(DataType type) async {...}
Android(iOS)側でInterfaceのコールを受け取る
Flutterアプリ側からメソッドのコールがあった際にonMethodCall()が必ず呼ばれる
引数のcall(型MethodCall)のmethodには、Interface側で定義した名前が渡される
code: HealthFitPlugin.kt
override fun onMethodCall(call: MethodCall, result: Result): Unit {
when (call.method) {
// Interface側(Dart)で定義されたメソッド名
"requestPermission" -> requestPermission(call, result)
}
}
Android(iOS)側でContextにどう対応するか
AndroidはiOSとメモリ管理の方法が異なり、Contextを参照することが多い
ヘルスデータの権限要求の際にも必要になる。
code: HealthFitPlugin.kt
GoogleSignIn.requestPermissions(
activity, // <- こいつがContext
GOOGLE_FIT_PERMISSIONS_REQUEST_CODE,
it,
optionsBuilder.build())
インスタンス生成の引数として渡す
code:HealthFitPlugin.kt
// このコンストラクタにActiviityを入れる
class HealthFitPlugin(private val activity: Activity) {
companion object {
@JvmStatic
fun registerWith(registrar: Registrar): Unit {
val plugin = HealthFitPlugin(registrar.activity()) // これがContext!!!!!!
val channel = MethodChannel(registrar.messenger(), "health_fit") // interfaceのファイル名適合
channel.setMethodCallHandler(plugin); // channelにコールバックをセット
}
}
}
AndroidのonActivityResult()にどう対応するか
Permissionをリクエストする際、Androidは以下の遷移が走る
1.アプリの画面 -> 2. requestPermission() -> 3.違う画面を起動 -> 4.許可する(拒否する) -> 5.元の画面に戻ってくる
1,2.https://gyazo.com/f27b46da5954ce7ae7ebac502b1b2d7f 3,4https://gyazo.com/091cf864b74942536f64c8880936dcac 5https://gyazo.com/f27b46da5954ce7ae7ebac502b1b2d7f
Android側では、元の画面(5)に戻る際にonActivityResult()というコールバックが呼ばれる。
実装手順
Android側にActivityResultListenerを実装する。
registartにコールバックを登録 (addActivityResultListener)
code: HealthFitPlugin.kt
class HealthFitPlugin(private val activity: Activity) : ActivityResultListener {
companion object {
@JvmStatic
fun registerWith(registrar: Registrar): Unit {
val plugin = HealthFitPlugin(registrar.activity())
// コールバックを登録する
registrar.addActivityResultListener(plugin)
}
}
}
// 認証画面から返ってくると呼ばれる
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Boolean = when (resultCode) {
// 処理を書く
}
結果をFlutterアプリに返す
onMethodCall(call: MethodCall, result: Result)の第二引数のresultに対してオブジェクトを関数に引き渡すと、Flutterアプリ側に結果が返される code:result
// 成功時
result.success("Any Object Here")
// 失敗時
result.error("errorCode", "errorMessage", "errorDetails"))
データの追加、取得、削除、更新
今回は取得のみ
必須パラメータを定義する
DataType
Step
Weight
DateTime
startAt データ集計の開始時間
DateTime
endAt データ集計の終了時間
TimeUnit
間引く時間単位
上記を決定し、iOS/Androidで問題ないかチェック
問題なければ実装に入る
データ取得のコード
Interface全体像
code:health_fit.dart
static Future<String> getData(DataType type, DateTime startAt, DateTime endAt, TimeUnit timeUnit) async {
final String data = await _channel.invokeMethod('getData', {
"dataType": type.toString(),
"startAt": startAt.millisecondsSinceEpoch,
"endAt": endAt.millisecondsSinceEpoch,
"timeUnit": timeUnit.toString(),
});
return data;
}
変数をAndroid(iOS)に渡す
key, valueを決定し、invokeMethodの第二引数に渡す
code:health_fit.dart
_channel.invokeMethod('getData', {"dataType": type.toString()});
Android(iOS)側で変数を取得する
onMethodCall(call: MethodCall, result: Result)の第一引数。call.argument<型>("Interfaceで定義している変数名")で渡されたデータを取得する
code:HealthFitPlugin.kt
private fun getDataType(call: MethodCall): DataType = when (call.argument<String>("dataType")) {
"DataType.STEP" -> DataType.TYPE_STEP_COUNT_DELTA
}
ソースコード
https://gyazo.com/21b27f32992f7115beb60725bf2be327
まとめ
Flutter Pluginの開発自体は手順を理解すれば難しくない。
iOS/Androidを実装レベルである程度できないとラーニングコストは高い(かも)
技術書典5参加します!
2018/10/8
https://gyazo.com/87c18d49dbcf013deeb85702e97a72bb
配置場所: う70
https://gyazo.com/049fc4a83b547882d88d0cd7ceb4af0e
We're Recruiting
https://gyazo.com/bea1ca9b71bbdce93867ee5057ba7664
おわり
Thank you!!